# -*- coding: utf-8 -*-
# Version: Python 3.9.5
# Author: TRIX
# Date: 2021-08-30 15:39:07
# Use: 使用前 保证需要计算的工作表符合如下规则：A2=业务量，B2=月份，C2=日期，D2=房间，E2=单位，F2=订房人，G2=总消费，H2=酸奶/烟/客损(减)，I2=应计业绩，J2=提成比例，K2=应提成，L2=个人业绩小计，M2=个人提成小计，第3行至第n行 第B列 第E列 第F列 第G列 第J列 含有相应数据 姓名 地点名字完整 不可简写 否则可能出现未知错误 计算方法： 按照 订房业绩明细表(1)(1)(1).xlsx 文件 数据处理规律 对选择的xlsx文件进行处理
# 对于每个 订房人 按照不同提成比例分别累计 业务量(行数) 总消费 酸奶/烟/客损(减) 应计业绩 应提成 最后再不区分 提成比例 进行总累计

from tkinter import *
from tkinter import messagebox#信息对话框
from tkinter import ttk#更好看的控件模块 但自定义不如不同类
from tkinter import filedialog#文件选择对话框
import openpyxl
from openpyxl.utils import column_index_from_string,get_column_letter
from operator import attrgetter
from openpyxl import Workbook
from openpyxl.styles import Font, Border, Side, Alignment#单元格格式
from decimal import Decimal, ROUND_HALF_UP#实现精确的四舍五入
from os.path import split

xlsx_format_list=[("all","*.*"),('xlsx','*.xlsx')]
def select_xlsx():#选择xlsx
	global xlsx
	global xlsx_file
	convert_label.configure(text='')
	xlsx_file=[filedialog.askopenfilename(filetypes=xlsx_format_list)]#弹出文件选择对话框并返回选择的文件名 选择单个文件 filetypes自定义可以选择和显示的文件类型
	if xlsx_file[-1]=='':return
	xlsx_file.extend(list(split(xlsx_file[0])))#返回路径与文件名组成的元组 不含/ [file,dirname,filename]
	if xlsx_file[-1][-5:]!='.xlsx':#如果格式不对
		xlsx_file=[]
		xlsx_prompt_label.configure(text='选择xlsx表格：')
		return messagebox.showerror(title='文件类型错误', message='文件格式只能是.xlsx，请重新选择，或将 .xls文件 另存为 .xlsx格式')#返回错误对话框
	xlsx_prompt_label.configure(text='你选择的是 <{}> 文件'.format(xlsx_file[-1]))
	sheet_prompt_label.configure(text='选择工作表：')
	xlsx=openpyxl.load_workbook(xlsx_file[0],data_only=True)#打开xlsx文件 类的实例化 data_only=True返回单元格的计算后的数字 False将返回单元格的原字符串
	select_sheet_box['values']=xlsx.sheetnames#定义列表内容
	select_sheet_box.configure(state='readonly')
	select_xlsx_button.configure(state=DISABLED)

def check_sheet(event):#检查sheet选择情况
	global sheet
	global xlsx_month_list
	if sheet_name.get() not in xlsx.sheetnames:
		return messagebox.showerror(title='工作表选择错误', message='工作表只能选择列表框显示的，不能自定义，请重新选择工作表')#返回错误对话框
	sheet_prompt_label.configure(text='你选择的是 <{}> 工作表'.format(sheet_name.get()))
	month_prompt_label.configure(text='选择月份：')
	sheet=xlsx[sheet_name.get()]#打开xlsx中对应名字的sheet
	xlsx_month_list=[]
	for row in sheet.iter_rows(min_row=3,min_col=1,max_row=sheet.max_row,max_col=13,values_only=False):#指定行:#所有数据行 每行单元格共组成一个元组 所有元组组成一个生成器
		if (row[1].value not in xlsx_month_list) and (isinstance(row[1].value,int)) and (1<=row[1].value<=12):
			xlsx_month_list.append(row[1].value)
	select_month_box['values']=xlsx_month_list#定义列表内容
	select_month_box.configure(state='readonly')
def check_month(event):
	global rows_list
	if int(month_name.get()) not in xlsx_month_list:
		return messagebox.showerror(title='月份选择错误', message='月份只能选择列表框显示的，不能自定义，请重新选择')#返回错误对话框
	month_prompt_label.configure(text='你选择的是 {}月数据'.format(month_name.get()))
	convert_button.configure(state=NORMAL)

	rows_list=[]
	for row in sheet.iter_rows(min_row=3,min_col=1,max_row=sheet.max_row,max_col=13,values_only=False):#指定行:#所有数据行 每行单元格共组成一个元组 所有元组组成一个生成器
		if row[1].value==int(month_name.get()) and row[5].value!=None:#如果每行的月份为等于选择的月份 且 有订房人
			rows_list.append(list(row))
		if any(row)==False:#如果遇到空行
			print(row)
			break
class staff_class():#员工类
	"""员工信息"""
	def __init__(self, name_unit,commission_rate_list):
		self.name_unit = name_unit#姓名和所在单位
		self.commission_rate_list = commission_rate_list#提成率类列表
		self.data_list=[]#行数据列表
	def get_staff_datas_row_list(self):#获取所有对应数据
		self.data_list=[]
		self.data_list.extend(self.name_unit.split('_'))
		self.commission_rate_list=sorted(self.commission_rate_list, key=attrgetter('rate', 'volume'),reverse=True)#先以 rate 再以 volume 来排序
		for commission_rate in self.commission_rate_list:
			self.data_list.extend(commission_rate.get_sig())
		if self.data_list[4]=='6%':#如果6%在前面 且只有6%
			for n in range(4):
				self.data_list.insert(2,0)
		elif self.data_list[4]=='8%' and len(self.data_list)==6:#如果8%在前面 且只有8%
			for n in range(4):
				self.data_list.append(0)
		self.data_list.append(0)
		for commission_rate in self.commission_rate_list:
			self.data_list[-1]+=commission_rate.get_commission()#计算多种提成比例之和
		self.data_list.extend([0,0])#签名栏 备注栏
		return self.data_list#姓名 单位 业务量 订房业绩 提成比例 提成金额 业务量 订座业绩 提成比例 提成金额 应提成合计金额
class commission_rate_class():#提成率类
	"""提成数据"""
	def __init__(self, rate,volume,consumption_list,depletion_list):
		self.rate = rate
		self.volume = volume#业绩量
		self.consumption_list = consumption_list#消费
		self.depletion_list = depletion_list#减损
		self.achievement=0#业绩
		self.commission=0#提成
	def get_consumption(self):
		return sum(consumption_list)
	def get_depletion(self):
		return sum(depletion_list)
	def get_achievement(self):#业绩算法
		self.achievement=0
		for i,consumption in enumerate(self.consumption_list):
			self.achievement+=consumption-self.depletion_list[i]
		return self.achievement
	def get_commission(self):#提成算法
		self.achievement=0
		self.commission=0
		self.commission=Decimal(str(self.get_achievement()*self.rate)).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
		return self.commission
	def get_rate(self):
		return '{:.0%}'.format(self.rate)
	def get_all(self):#获取所有数据
		return [self.volume,self.get_achievement(),self.get_rate(),self.get_commission(),self.get_consumption(),self.get_depletion(),]
	def get_sig(self):#获取所有有用的数据
		return [self.volume,self.get_achievement(),self.get_rate(),self.get_commission(),]
class row_class():
	"""每一行作为一个对象"""
	def __init__(self, name_unit,consumption, depletion,rate):
		self.name_unit=name_unit
		self.consumption=consumption
		self.depletion=depletion
		self.rate=rate
	def __repr__(self):#打印调用时 返回值
		return repr((self.name_unit,self.consumption,self.depletion,self.rate))

def sheet_convert():
	global rows_list
	global convert_time
	select_xlsx_button.configure(state=DISABLED)
	select_sheet_box.configure(state=DISABLED)
	select_month_box.configure(state=DISABLED)
	convert_button.configure(state=DISABLED)
	for i,row in enumerate(rows_list):#提取列表中的数据列
		for index,element in enumerate(row):
			if element.value==None:#如果为空值 修改为0
				row[index].value=0
		rows_list[i]=row_class(row[5].value+'_'+row[4].value,row[6].value,row[7].value,row[9].value)
	rows_list=sorted(rows_list, key=attrgetter('name_unit', 'rate'))#先以name_unit再以rate来排序

	#将数据顺序调整成容易处理的顺序
	staff_dict={}
	for row in rows_list:#{name:rate:[[consumption,depletion],]}
		staff_dict.setdefault(row.name_unit,{})
		staff_dict[row.name_unit].setdefault(row.rate,[])
		staff_dict[row.name_unit][row.rate].append([row.consumption,row.depletion])
	staff_list=[]
	for name_unit,commission_dict in staff_dict.items():#[staff_class,]
		commission_rate_list=[]
		for rate,datas_list in commission_dict.items():
			consumption_list=[]
			depletion_list=[]
			for data_list in datas_list:
				consumption_list.append(data_list[0])
				depletion_list.append(data_list[1])
			commission_rate=commission_rate_class(rate,len(datas_list),consumption_list,depletion_list)
			commission_rate_list.append(commission_rate)
		staff=staff_class(name_unit,commission_rate_list)
		staff_list.append(staff)

	def cell_format(acell,isbold=False,isframe=True,size=13):
		acell.font=Font(name='等线', size=size,bold=isbold)#等线 13
		acell.alignment=Alignment(horizontal='center', vertical='center')#水平居中 竖直居中
		if isframe:
			left, right, top, bottom = [Side(style='thin', color='000000')] * 4
			acell.border = Border(left=left, right=right, top=top, bottom=bottom)#黑色边框
	result_xlsx=Workbook()#新建结果xlsx
	result_sheet=result_xlsx.create_sheet('{}月员工提成表'.format(month_name.get()),0)#新建sheet 插到索引位 索引值从0开始 不填默认插到最后

	cell_format(result_sheet.cell(row=1,column=1,value='{}月员工订房业绩提成表'.format(month_name.get())),isbold=True,size=18)
	cell_format(result_sheet.cell(row=2,column=1,value='姓名'))
	cell_format(result_sheet.cell(row=2,column=2,value='单位'))
	cell_format(result_sheet.cell(row=2,column=3,value='包    房    8%'))
	cell_format(result_sheet.cell(row=2,column=7,value='卡    座    6%'))
	cell_format(result_sheet.cell(row=2,column=11,value='合计提成'))
	cell_format(result_sheet.cell(row=2,column=12,value='领款签名'))
	cell_format(result_sheet.cell(row=2,column=13,value='备注'))
	result_data_type='业务量 订房业绩 提成比例 提成金额 业务量 订座业绩 提成比例 提成金额'.split()
	for i,e in enumerate(result_data_type,3):#位置从1开始
		acell=result_sheet.cell(row=3,column=i,value=e)
		cell_format(acell)

	total_data_list=[0 for n in range(13)]
	total_data_list[0]='合计'
	for r,staff in enumerate(staff_list,4):
		staff.get_staff_datas_row_list()
		for c,data in enumerate(staff.data_list,1):
			acell=result_sheet.cell(row=r,column=c,value=data)
			cell_format(acell)
		total_data_list[2]+=staff.data_list[2]
		total_data_list[3]+=staff.data_list[3]
		total_data_list[5]+=staff.data_list[5]
		total_data_list[6]+=staff.data_list[6]
		total_data_list[7]+=staff.data_list[7]
		total_data_list[9]+=staff.data_list[9]
		total_data_list[10]+=staff.data_list[10]

	staff_amount=len(staff_list)+4
	for i,e in enumerate(total_data_list,1):#位置从1开始
		if e==0:
			total_data_list[i-1]=None
		acell=result_sheet.cell(row=staff_amount,column=i,value=e)
		cell_format(acell,isbold=True)
	cell_format(result_sheet.cell(row=staff_amount+1,column=1,value='制表：'),isframe=False)
	cell_format(result_sheet.cell(row=staff_amount+1,column=2,value='TRIX'),isframe=False)
	cell_format(result_sheet.cell(row=staff_amount+1,column=4,value='部门经理：'),isframe=False)
	cell_format(result_sheet.cell(row=staff_amount+1,column=7,value='财务经理：'),isframe=False)
	cell_format(result_sheet.cell(row=staff_amount+1,column=10,value='核准：'),isframe=False)

	for i,row in enumerate(list(result_sheet.rows)):
		for cell in row:
			if cell.value==0:
				cell.value=None
		result_sheet.row_dimensions[i+1].height = 24#行高 类似列表切片 默认12.75
	result_sheet.row_dimensions[1].height = 28#行高 类似列表切片 默认12.75
	for col in range(1,12):
		result_sheet.column_dimensions[get_column_letter(col)].width = 11#列宽 类似列表切片 默认8.43
	result_sheet.column_dimensions['L'].width = 15
	result_sheet.column_dimensions['M'].width = 15
	result_sheet.merge_cells('A1:M1')#合并一个矩形区域中的单元格
	result_sheet.merge_cells('C2:F2')
	result_sheet.merge_cells('G2:J2')
	result_sheet.merge_cells('A2:A3')
	result_sheet.merge_cells('B2:B3')
	result_sheet.merge_cells('K2:K3')
	result_sheet.merge_cells('L2:L3')
	result_sheet.merge_cells('M2:M3')
	result_string='{}/{}月员工提成表.xlsx'.format(xlsx_file[1],month_name.get())
	result_xlsx.save(result_string)#程序结束时使用 只支持xlsx格式
	select_xlsx_button.configure(state=NORMAL)
	xlsx_prompt_label.configure(text='')
	sheet_prompt_label.configure(text='')
	month_prompt_label.configure(text='')
	select_sheet_box['values']=[]
	select_month_box['values']=[]
	if len(result_string)>40:
		result_string=result_string[:20]+'...'+result_string[-16:]
	convert_label.configure(text='转换完成，你可以选择文件继续转换。\n结果表格目录：{}\n工作表名称：{}月员工提成表'.format(result_string,month_name.get()))

msyh_15=('msyh', 15)#字体格式
window=Tk()#创建主窗口
window.title('每月员工提成计算器-By_TRIX')#窗口标题
program_prompt_label=ttk.Label(window,text=
'''使用程序前，请确保需要计算的工作表符合如下规则，否则可能出现未知错误：
	A2=业务量，B2=月份，C2=日期，D2=房间，E2=单位，F2=订房人，
	G2=总消费，H2=酸奶/烟/客损(减)，I2=应计业绩，J2=提成比例，
	K2=应提成，L2=个人业绩小计，M2=个人提成小计，
	第3行至第n行数据的第B列 第E列 第F列 第G列 第J列 含有相应数据，
	数字数据不含单位或符号(%除外)，数字为阿拉伯数字，包括日期数字，
	最后一行数据与合计行之间至少存在一行空行(空值)，
	姓名和地点名字完整，不可简写。
计算方法：
	按照 <表格模板.xlsx> 文件 的 <工作表基本格式> 工作表结构
	对选择的xlsx文件进行数据处理。
出现 “转换完成” 字样则说明转换成功，否则可能由于操作不当使程序出错。''',font=msyh_15)
# 对于每个 订房人 按照不同提成比例分别累计 业务量(行数) 总消费 酸奶/烟/客损(减) 应计业绩 应提成 最后再不区分 提成比例 进行总累计')#格式提示标签
xlsx_prompt_label=ttk.Label(window,text='选择xlsx表格：',font=msyh_15)
select_xlsx_button=ttk.Button(window,text='选择文件', command=select_xlsx)#文件选择按钮

sheet_prompt_label=ttk.Label(window,text='',font=msyh_15)
sheet_name=StringVar()
select_sheet_box=ttk.Combobox(window,width=10,textvariable=sheet_name,state=DISABLED)#工作表选择列表
select_sheet_box.bind('<<ComboboxSelected>>', check_sheet)

month_prompt_label=ttk.Label(window,text='',font=msyh_15)
month_name=StringVar()
select_month_box=ttk.Combobox(window,width=10,textvariable=month_name,state=DISABLED)#月份选择列表
select_month_box.bind('<<ComboboxSelected>>', check_month)

convert_button=ttk.Button(window,text='开始转换', command=sheet_convert,state=DISABLED)#转换按钮
convert_label=ttk.Label(window,text='',font=msyh_15)

window.geometry('750x480') #窗口大小 小写英文字母 x
program_prompt_label.place(x=20,y=10)#放置所有控件
xlsx_prompt_label.place(x=20,y=270)
select_xlsx_button.place(x=120,y=360)

sheet_prompt_label.place(x=20,y=300)
select_sheet_box.place(x=240,y=362)

month_prompt_label.place(x=20,y=330)
select_month_box.place(x=360,y=362)

convert_button.place(x=480,y=360)
convert_label.place(x=20,y=390)

window.resizable(False,False)#x y轴都不能改变窗口大小
window.iconbitmap('icon.ico')#设置缩略图
window.mainloop()#保持窗口打开状态
#demand_prompt_label=ttk.Label(window,text='只用于对 <订房业绩明细表.xlsx 文件> 进行如下处理：\n对于每个 [订房人] 按照不同 [提成比例] 分别累计 [业务量] [总消费] [酸奶/烟/客损(减)] [应计业绩] [应提成] \n最后再不区分 提成比例 进行总累计',font=arial_12)#需求提示标签
